﻿using System;
using System.IO;
using System.Text;

namespace Exif.Various
{
	///<summary>
	///Бинари ридер с возможностью смены порядка байт
	///</summary>
	///<remarks>Работаем из условия, что MS'ий <see cref="BinaryReader"/> работает
	///только в <see cref="Various.Endian.Little"/></remarks>
	public class EndianBinaryReader : BinaryReader
	{
#if ! __UNSAFE

		#region Nested type

		private class CustomEndianMemoryStream : MemoryStream
		{
			#region Частные методы класса

			private readonly byte[] _buffer;

			private readonly object _syncObj = new object();

			#endregion

			#region Конструктор

			public CustomEndianMemoryStream(int capacity)
				: base(new byte[capacity], 0, capacity, true, true)
			{
				_buffer = base.GetBuffer();
			}

			#endregion

			#region Свойства класса

			public object SyncRoot
			{
				get { return this._syncObj; }
			}

			#endregion

			#region Методы класса

			///<summary>
			///Сохраняет число в буфер
			///</summary>
			///<param name="value">Число</param>
			///<param name="endian">Требуемый при чтении порядок байт</param>
			public void Put(byte[] value, Endian endian)
			{
				var len = value.Length;
				switch (endian)
				{
					case Endian.Little:
						for (var i = 0; i < len; i++)
						{
							this._buffer[i] = value[i];
						}
						break;

					case Endian.Big:
						for (var i = 0; i < len; i++)
						{
							//эмуляция прямого порядка:
							//инвертируем, ибо BinaryReader читает только обратный порядок.
							this._buffer[len - i] = value[i];
						}
						break;
				}
			}

			#endregion
		}

		#endregion

		#region Частные поля класса

		private readonly CustomEndianMemoryStream _ms;

		private readonly BinaryReader _msReader;

		#endregion

#endif

		#region Конструктор

		///<summary>
		///Конструктор
		///</summary>
		///<param name="input">Низлежащий поток</param>
		///<param name="encoding">Кодировка</param>
		///<param name="endian">Порядок байт</param>
		public EndianBinaryReader(Stream input, Encoding encoding, Endian endian)
			: base(input, encoding)
		{
			this.Endian = endian;
#if ! __UNSAFE
			this._ms = new CustomEndianMemoryStream(0x10);
			this._msReader = new BinaryReader(_ms, encoding);
#endif
		}

		///<summary>
		///Конструктор (little-endian)
		///</summary>
		///<param name="input">Низлежащий поток</param>
		public EndianBinaryReader(Stream input)
			: this(input, Endian.Little)
		{
		}

		///<summary>
		///Конструктор
		///</summary>
		///<param name="input">Низлежащий поток</param>
		///<param name="endian">Порядок байт</param>
		public EndianBinaryReader(Stream input, Endian endian)
			: this(input, new UTF8Encoding(), endian)
		{
		}

		///<summary>
		///Конструктор (little-endian)
		///</summary>
		///<param name="input">Низлежащий поток</param>
		///<param name="encoding">Кодировка</param>
		public EndianBinaryReader(Stream input, Encoding encoding)
			: this(input, encoding, Endian.Little)
		{
		}

		#endregion

		#region Свойства класса

		///<summary>
		///
		///</summary>
		public Endian Endian { get; set; }

		#endregion

		#region Перегруженные методы класса

		///<summary>Reads an 8-byte unsigned integer from the current stream and advances the position of the stream by eight bytes.</summary>
		///<returns>An 8-byte unsigned integer read from this stream.</returns>
		///<exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		///<exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		///<exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception><filterpriority>2</filterpriority>
		public override ulong ReadUInt64()
		{
			if (Endian == Endian.Little)
			{
				return base.ReadUInt64();
			}

			var buffer = base.ReadBytes(8);
			var num = (uint)(((buffer[0] | (buffer[1] << 8)) | (buffer[2] << 0x10)) | (buffer[3] << 0x18));
			var num2 = (uint)(((buffer[4] | (buffer[5] << 8)) | (buffer[6] << 0x10)) | (buffer[7] << 0x18));
			return ((num2 << 0x20) | num);
		}

		///<summary>Reads a 4-byte unsigned integer from the current stream and advances the position of the stream by four bytes.</summary>
		///<returns>A 4-byte unsigned integer read from this stream.</returns>
		///<exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		///<exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		///<exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception><filterpriority>2</filterpriority>
		public override uint ReadUInt32()
		{
			if (Endian == Endian.Little)
			{
				return base.ReadUInt32();
			}

			var buffer = base.ReadBytes(0x4);
			return (uint)((((buffer[0] << 0x18) | (buffer[1] << 0x10)) | (buffer[2] << 8)) | buffer[3]);
		}

		///<summary>Reads a 2-byte unsigned integer from the current stream using little endian encoding and advances the position of the stream by two bytes.</summary>
		///<returns>A 2-byte unsigned integer read from this stream.</returns>
		///<exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		///<exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		///<exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception><filterpriority>2</filterpriority>
		public override ushort ReadUInt16()
		{
			if (Endian == Endian.Little)
			{
				return base.ReadUInt16();
			}

			var buffer = base.ReadBytes(0x2);
			return (ushort)((buffer[0] << 8) | buffer[1]);
		}

		///<summary>Reads a 4-byte floating point value from the current stream and advances the current position of the stream by four bytes.</summary>
		///<returns>A 4-byte floating point value read from the current stream.</returns>
		///<exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		///<exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		///<exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception><filterpriority>2</filterpriority>
		public override
#if __UNSAFE
			unsafe
#endif
			float ReadSingle()
		{
			if (Endian == Endian.Little)
			{
				return base.ReadSingle();
			}

			var buffer = base.ReadBytes(0x4);
#if __UNSAFE
			uint num = (uint)((((buffer[0] << 0x18) | (buffer[1] << 0x10)) | (buffer[2] << 8)) | buffer[3]);
			return *(((float*)&num));
#else
			lock (_ms.SyncRoot)
			{
				_ms.Put(buffer, Endian.Big);
				return _msReader.ReadSingle();
			}
#endif
		}

		///<summary>Reads an 8-byte signed integer from the current stream and advances the current position of the stream by eight bytes.</summary>
		///<returns>An 8-byte signed integer read from the current stream.</returns>
		///<exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		///<exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		///<exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception><filterpriority>2</filterpriority>
		public override long ReadInt64()
		{
			if (Endian == Endian.Little)
			{
				return base.ReadInt64();
			}

			var buffer = base.ReadBytes(0x8);
			var num = (uint)((((buffer[0] << 0x18) | (buffer[1] << 0x10)) | (buffer[2] << 8)) | buffer[3]);
			var num2 = (uint)((((buffer[4] << 0x18) | (buffer[5] << 0x10)) | (buffer[6] << 8)) | buffer[7]);
			return (long)((num << 0x20) | num2);
		}

		///<summary>Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes.</summary>
		///<returns>A 4-byte signed integer read from the current stream.</returns>
		///<exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		///<exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		///<exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception><filterpriority>2</filterpriority>
		public override int ReadInt32()
		{
			if (Endian == Endian.Little)
			{
				return base.ReadInt32();
			}

			var buffer = base.ReadBytes(0x4);
			return ((((buffer[0] << 0x18) | (buffer[1] << 0x10)) | (buffer[2] << 0x8)) | buffer[3]);
		}

		///<summary>Reads a 2-byte signed integer from the current stream and advances the current position of the stream by two bytes.</summary>
		///<returns>A 2-byte signed integer read from the current stream.</returns>
		///<exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		///<exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		///<exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception><filterpriority>2</filterpriority>
		public override short ReadInt16()
		{
			if (Endian == Endian.Little)
			{
				return base.ReadInt16();
			}

			var buffer = base.ReadBytes(0x2);
			return (short)((buffer[0] << 0x8) | buffer[1]);
		}

		///<summary>Reads an 8-byte floating point value from the current stream and advances the current position of the stream by eight bytes.</summary>
		///<returns>An 8-byte floating point value read from the current stream.</returns>
		///<exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		///<exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		///<exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception><filterpriority>2</filterpriority>
		public override
#if __UNSAFE
			unsafe
#endif
			double ReadDouble()
		{
			if (Endian == Endian.Little)
			{
				return base.ReadDouble();
			}

			var buffer = base.ReadBytes(0x8);
#if __UNSAFE
			var num = (uint)(((buffer[3] | (buffer[2] << 8)) | (buffer[1] << 0x10)) | (buffer[0] << 0x18));
			var num2 = (uint)(((buffer[7] | (buffer[6] << 8)) | (buffer[5] << 0x10)) | (buffer[4] << 0x18));
			ulong num3 = (num << 0x20) | num2;
			return *(((double*)&num3));
#else
			lock (_ms.SyncRoot)
			{
				_ms.Put(buffer, Endian.Big);
				return _msReader.ReadDouble();
			}
#endif
		}

		///<summary>Reads a decimal value from the current stream and advances the current position of the stream by sixteen bytes.</summary>
		///<returns>A decimal value read from the current stream.</returns>
		///<exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		///<exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		///<exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception><filterpriority>2</filterpriority>
		public override decimal ReadDecimal()
		{
			if (Endian == Endian.Little)
			{
				return base.ReadDecimal();
			}

			var buffer = base.ReadBytes(0x10);
#if __UNSAFE
			throw new NotImplementedException("Потом будет");
#else
			lock (_ms.SyncRoot)
			{
				_ms.Put(buffer, Endian.Big);
				return _msReader.ReadDecimal();
			}
#endif
		}

		#endregion

		#region Частные статик методы

		//internal static decimal ToDecimal(byte[] buffer)
		//{
		//    var lo = ((buffer[0] | (buffer[1] << 8)) | (buffer[2] << 0x10)) | (buffer[3] << 0x18);
		//    var mid = ((buffer[4] | (buffer[5] << 8)) | (buffer[6] << 0x10)) | (buffer[7] << 0x18);
		//    var hi = ((buffer[8] | (buffer[9] << 8)) | (buffer[10] << 0x10)) | (buffer[11] << 0x18);
		//    return new decimal(lo, mid, hi, ((buffer[12] | (buffer[13] << 8)) | (buffer[14] << 0x10)) | (buffer[15] << 0x18));
		//}

		#endregion
	}
}